package com.tarena.lbs.marketing.web.service;

import com.alibaba.nacos.common.utils.CollectionUtils;
import com.tarena.lbs.attach.api.AttachApi;
import com.tarena.lbs.base.common.utils.Asserts;
import com.tarena.lbs.base.protocol.exception.BusinessException;
import com.tarena.lbs.base.protocol.pager.PageResult;
import com.tarena.lbs.common.passport.principle.UserPrinciple;
import com.tarena.lbs.common.security.utils.LbsSecurityContenxt;
import com.tarena.lbs.marketing.web.repository.ActivityRepository;
import com.tarena.lbs.marketing.web.repository.CouponCodeRepository;
import com.tarena.lbs.marketing.web.repository.CouponRepository;
import com.tarena.lbs.marketing.web.repository.UserCouponsRepository;
import com.tarena.lbs.pojo.attach.dto.AttachQrDTO;
import com.tarena.lbs.pojo.attach.param.AttachQRParam;
import com.tarena.lbs.pojo.basic.event.LocationStoreEvent;
import com.tarena.lbs.pojo.marketing.param.UserCouponsParam;
import com.tarena.lbs.pojo.marketing.po.ActivityPO;
import com.tarena.lbs.pojo.marketing.po.CouponCodePO;
import com.tarena.lbs.pojo.marketing.po.CouponPO;
import com.tarena.lbs.pojo.marketing.po.UserCouponsPO;
import com.tarena.lbs.pojo.marketing.query.UserCouponCodeQuery;
import com.tarena.lbs.pojo.marketing.query.UserCouponQuery;
import com.tarena.lbs.pojo.marketing.vo.UserCouponsVO;
import com.tarena.lbs.stock.api.StockApi;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Service
@Slf4j
public class UserCouponsService {
    @Autowired
    private ActivityRepository activityRepository;
    @Autowired
    private ActivityService activityService;
    @Autowired
    private CouponRepository couponRepository;
    @Autowired
    private UserCouponsRepository userCouponsRepository;
    @Autowired
    private CouponCodeRepository couponCodeRepository;
    @DubboReference
    private StockApi stockApi;
    //redis客户端
    @Autowired
    private RedisTemplate redisTemplate;
    @DubboReference
    private AttachApi attachApi;
    public void receiveCoupon(UserCouponsParam param) throws BusinessException {
        //1.读取认证对象,获取用户的id
        Integer userId=checkAuthUser();
        //2.1读取param中活动id的详情
        Integer activityId = param.getActivityId();
        ActivityPO activityPO=activityRepository.getActivityById(activityId);
        //2.2判断活动合法
        checkActivity(activityPO);
        //3.userId activityId验证目标所属
        checkIsTargetConsumer(userId,activityPO);
        //4.1 利用param 优惠券id查询详情
        Integer couponId = param.getCouponId();
        CouponPO couponPO = couponRepository.getCouponById(couponId);
        //4.2 验证合法
        checkCoupon(couponPO);
        //5.领取记录 该用户userId 已经领取couponId多少次 count 对比优惠券上限
        checkUsageLimit(userId,couponPO);
        //6.领取优惠券
        receiveCoupon(userId,couponPO,param);
    }
    //保证数据一致 领了 券码状态改 领取记录增 库存减
    @Transactional(rollbackFor = Exception.class)
    public void receiveCoupon(Integer userId,CouponPO couponPo,UserCouponsParam param) throws BusinessException {
        //操作redis的String类型的数据 命令 setnx
        ValueOperations opsForValue = redisTemplate.opsForValue();
        // 1.读取库存数量 不足 ==0 结束了
        CouponCodePO couponCodePO=null;
        boolean tryLock=false;//抢锁结果
        String lockKey="";
        do{
        boolean enough = stockApi.isEnough(couponPo.getId());
        Asserts.isTrue(!enough,new BusinessException("-2","库存不足"));
        //2.获取一个可用的券码(从一堆没领取的状态是0的券码中 拿到一个券码)
         couponCodePO=couponCodeRepository.getOneCouponCode(couponPo.getId());
        Asserts.isTrue(couponCodePO==null,new BusinessException("-2","券码不足"));
        //执行抢锁 setnex key value expire 10 所有争抢的逻辑,一定有一个核心争抢资源.这里争抢的核心资源
            // 是券码 锁 设计一个和券码有关的key值
        lockKey="marketing:coupon:receive:"+couponCodePO.getCouponCode();
        //redis抢锁
            tryLock=opsForValue.setIfAbsent(lockKey,"",10, TimeUnit.SECONDS);
        }while (!tryLock);
        try{
            this.doReceiveCoupon(userId,couponPo,couponCodePO,param);
        }catch (Exception e){
            throw new BusinessException("-2",e.getMessage());
        }finally {
            //释放锁 要写在finally
            opsForValue.getAndDelete(lockKey);
        }
    }
        private void doReceiveCoupon(Integer userId, CouponPO couponPo, CouponCodePO couponCodePO,UserCouponsParam param) throws BusinessException {
        //3.1封装写入的数据po 大部分属性都和优惠券相同
        UserCouponsPO userCouponsPO=new UserCouponsPO();
        BeanUtils.copyProperties(couponPo,userCouponsPO);
        userCouponsPO.setId(null);//如果不设置 每次领取记录都是优惠券id 无法领取第二次
        userCouponsPO.setUserId(userId);
        userCouponsPO.setCouponId(couponPo.getId());
        userCouponsPO.setCouponCode(couponCodePO.getCouponCode());
        userCouponsPO.setCreateAt(new Date());
        userCouponsPO.setUpdateAt(userCouponsPO.getCreateAt());
        //手动补充一个领取渠道 活动id 店铺id status启用
        userCouponsPO.setReceiveChannel(1);
        userCouponsPO.setActivityId(param.getActivityId());
        userCouponsPO.setShopId(param.getShopId());
        userCouponsPO.setCouponValue(couponPo.getDiscountValue());
        log.info("当前领取记录数据:{}",userCouponsPO);
        //3.2写入数据库
        userCouponsRepository.saveUserCoupons(userCouponsPO);

        // 4.修改该券码状态 1
        couponCodeRepository.updateCouponCodeStatus(couponCodePO.getId(),1);
        //5.减少库存
        boolean result = stockApi.reduceStock(couponPo.getId());
        //库存减少 有可能失败,手动向外抛出异常,实现上述步骤中本地数据库写操作的回滚
        Asserts.isTrue(!result,new BusinessException("-2","减库存失败"));
    }
    private void checkUsageLimit(Integer userId, CouponPO couponPO) throws BusinessException {
        //如果userId已经领取过该优惠券,userId couponId在 user_coupons表里 是有记录的
        //读取数据库表格记录 检查该用户领取该优惠券次数 null <上限 =上限
        Long total=userCouponsRepository.countReceiveTimes(userId,couponPO.getId());
        log.info("当前用户:{},领取优惠券:{},总数:{}",userId,couponPO.getId(),total);
        Integer maxLimit = couponPO.getUsageLimit();
        log.info("当前优惠券上限:{}",maxLimit);
        Asserts.isTrue(total.equals(maxLimit.longValue()),new BusinessException("-2","该用户领取已达上限"));
    }

    private void checkCoupon(CouponPO couponPO) throws BusinessException {
        //注意: 优惠券时间不能作为合法的判断 应该消费的时候 展示给商家使用优惠券的时候
        Asserts.isTrue(couponPO==null,new BusinessException("-2","优惠券不存在"));
        //enableStatus禁用
        Asserts.isTrue(couponPO.getEnableStatus()==1,new BusinessException("-2","优惠券禁用"));
    }

    private void checkIsTargetConsumer(Integer userId, ActivityPO activityPO) throws BusinessException {
        boolean result=activityService.isTargetConsumer(userId,activityPO.getId());
        Asserts.isTrue(!result,new BusinessException("-2","用户不是该活动的目标人群"));
    }

    private void checkActivity(ActivityPO activityPO) throws BusinessException {
        //1.是否存在活动
        Asserts.isTrue(activityPO==null,new BusinessException("-2","活动不存在"));
        //2.时间是否合法
        Date now=new Date();
        Asserts.isTrue(now.before(activityPO.getStartDate())||now.after(activityPO.getEndDate()),new BusinessException("-2","活动时间不合法"));
        //3.enableStatus是否是0 启用
        Asserts.isTrue(activityPO.getEnableStatus()==1,new BusinessException("-2","活动已被禁用"));
    }

    private Integer checkAuthUser() throws BusinessException {
        UserPrinciple userPrinciple = LbsSecurityContenxt.getLoginToken();
        Asserts.isTrue(userPrinciple==null,new BusinessException("-2","用户解析认证失败"));
        return userPrinciple.getId();
    }

    public PageResult<UserCouponsVO> showMyCoupons(UserCouponQuery query) throws BusinessException {
        UserPrinciple userPrinciple = LbsSecurityContenxt.getLoginToken();
        Asserts.isTrue(userPrinciple==null,new BusinessException("-2","用户解析认证失败"));
        query.setUserId(userPrinciple.getId());
        PageResult<UserCouponsVO> voPage=new PageResult<>();
        voPage.setPageNo(1);
        voPage.setPageSize(20);
        voPage.setTotal(100L);
        //从仓储层查询list
        List<UserCouponsPO> pos=userCouponsRepository.findAll(query);
        List<UserCouponsVO> vos=null;
        if (CollectionUtils.isNotEmpty(pos)){
            vos=pos.stream().map(po->{
                UserCouponsVO vo=new UserCouponsVO();
                BeanUtils.copyProperties(po,vo);
                return vo;
            }).collect(Collectors.toList());
        }
        voPage.setObjects(vos);
        return voPage;
    }


    public UserCouponsVO userCouponDetail(UserCouponCodeQuery query) {
        ValueOperations opsForValue = redisTemplate.opsForValue();
        //1.使用券码code编码 查询userCoupons
        UserCouponsPO po=userCouponsRepository.getUserCouponsByCode(query.getCouponCode());
        UserCouponsVO vo=null;
        if (po!=null){
            vo=new UserCouponsVO();
            BeanUtils.copyProperties(po,vo);
            //补充vo中的二维码url地址 不一定远程调用attach
            String qrCodeKey="marketing:user:coupons:qr:"+query.getCouponCode();
            String url = (String) opsForValue.get(qrCodeKey);
            if (url!=null){
                log.info("本次请求缓存命中:{}",url);
                //缓存命中
            }else{
                log.info("本次请求缓存没命中:{}",url);
                //缓存没命中
                //远程调用attach服务 补充二维码连接地址
                AttachQRParam attachQRParam=new AttachQRParam();
                attachQRParam.setBusinessId(po.getId());
                attachQRParam.setBusinessType(800);
                //content应该是扫码时,手机端可以发送请求的一个地址 创建订单 使用优惠券
                //String content="http://tarena.lbs.order.com/create/order/code/"+po.getCouponCode();
                attachQRParam.setContent("http://www.baidu.com?code="+po.getCouponCode());
                AttachQrDTO attachQrDTO = attachApi.generateQrCode(attachQRParam);
                url=attachQrDTO.getUrl();
                //在缓存补充这个数据 下次访问 就命中数据了
                opsForValue.set(qrCodeKey,url);
            }
            vo.setCouponUrl(url);
            return vo;

        }else{
            return null;
        }
    }

    public void autoReleaseCoupons(LocationStoreEvent event) throws BusinessException {
        //1.使用店铺id查询所有活动信息 list activities
        List<Integer> storeIds = event.getStoreIds();
        Integer userId=event.getUserId();
        //如果是空的,业务不执行,本次消费结束
        Asserts.isTrue(CollectionUtils.isEmpty(storeIds)||userId==null,new BusinessException("-2","自动下发优惠券入参异常"));
        List<ActivityPO> activityPos=activityRepository.getActivitiesByStores(storeIds);
        //2.封装单个领取优惠券的UserCouponsParam的列表 将activityPos转化成List<UserCouponsParam>
        Asserts.isTrue(CollectionUtils.isEmpty(activityPos),new BusinessException("-2","活动为空消费逻辑停止"));
        List<UserCouponsParam> params=
                activityPos.stream().map(activityPO -> {
                    UserCouponsParam param=new UserCouponsParam();
                    param.setUserId(userId);
                    param.setActivityId(activityPO.getId());
                    param.setCouponId(activityPO.getRewardCouponId());
                    //缺少店铺id
                    String shopIds = activityPO.getShopIds();
                    param.setShopId(Integer.valueOf(shopIds));
                    return param;
                }).collect(Collectors.toList());
        //3.循环调用 领取功能 如果活动不合法 优惠券不合法 用户不是目标人群 库存没有了 领取上限 都会抛异常
        this.autoReleaseCoupons(params);
    }

    private void autoReleaseCoupons(List<UserCouponsParam> params) {
        for (UserCouponsParam param : params) {
            try{
                this.releaseCoupon(param);
            }catch (BusinessException e){
                log.info("自动下发给用户:{},优惠券:{} 失败",param.getUserId(),param.getCouponId(),e);
            }
        }
    }
    private void releaseCoupon(UserCouponsParam param) throws BusinessException {
        //2.1读取param中活动id的详情
        Integer activityId = param.getActivityId();
        ActivityPO activityPO=activityRepository.getActivityById(activityId);
        //2.2判断活动合法
        checkActivity(activityPO);
        //3.userId activityId验证目标所属
        checkIsTargetConsumer(param.getUserId(),activityPO);
        //4.1 利用param 优惠券id查询详情
        Integer couponId = param.getCouponId();
        CouponPO couponPO = couponRepository.getCouponById(couponId);
        //4.2 验证合法
        checkCoupon(couponPO);
        //5.领取记录 该用户userId 已经领取couponId多少次 count 对比优惠券上限
        checkUsageLimit(param.getUserId(),couponPO);
        //6.领取优惠券
        receiveCoupon(param.getUserId(),couponPO,param);
    }

}
